package com.zsd.android.complier;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.zsd.android.annotation.Subscribe;
import com.zsd.android.annotation.mode.EventBeans;
import com.zsd.android.annotation.mode.SubscribeInfo;
import com.zsd.android.annotation.mode.SubscriberMethod;
import com.zsd.android.annotation.mode.ThreadMode;
import com.zsd.android.complier.utils.Constants;
import com.zsd.android.complier.utils.TextUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({Constants.SUBSCRIBE_ANNOTATION_TYPE})
@SupportedOptions({Constants.PACKAGE_NAME, Constants.CLASS_NAME})
public class SubscribeProcessor extends AbstractProcessor {

    private Elements elementUtils;
    private Types typeUtils;
    private Filer filer;
    private Messager messager;
    //APT包名
    private String packageName;
    //APT类名
    private String className;
    //临时Map存储，用来存放订阅方法信息，生成路由组类文件时遍历
    //key：组名"MainActivity", value:MainActivity中订阅方法集合
    private final Map<TypeElement, List<ExecutableElement>> methodsByClass = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        Map<String, String> options = processingEnvironment.getOptions();
        if (!TextUtils.isEmpty(options)) {
            packageName = options.get(Constants.PACKAGE_NAME);
            className = options.get(Constants.CLASS_NAME);
            messager.printMessage(Diagnostic.Kind.NOTE, "packageName = " + packageName + ", className = " + className);
        }
        if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "注解处理器需要的参数为空，请在对应build.gradle配置参数");
        }
    }

    /**
     * 相当于main函数开始处理注解
     * 处理注解器的核心方法，处理具体的注解，生成Java文件
     *
     * @param set 使用了支持处理
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (!TextUtils.isEmpty(set)) {
            //一旦有方法之上使用了@Subscribe注解
            //获取所有被@Subscribe注解的元素集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Subscribe.class);
            if (!TextUtils.isEmpty(elements)) {
                //解析元素
                parserElements(elements);
                TypeElement subscribeIndexType = elementUtils.getTypeElement(Constants.SUBSCRIBERINFO_INDEX);
                //生成java文件
                createFile(subscribeIndexType);
            }
            return true;
        }
        return false;
    }

    private void parserElements(Set<? extends Element> elements) {
        for (Element element : elements) {
            //@Subscribe注解只能在方法之上（尽量避免使用instanceOf进行判断）
            if (element.getKind() != ElementKind.METHOD) {
                messager.printMessage(Diagnostic.Kind.ERROR, "仅解析@Subscribe注解在方法上的元素");
                return;
            }
            // 强转方法元素
            ExecutableElement method = (ExecutableElement) element;
            //检查方法，条件：订阅方法必须是非静态,公开的，参数只有一个
            if (checkMethod(method)) {
                //获取封装订阅的方法（方法上一个节点）
                TypeElement classElement = (TypeElement) method.getEnclosingElement();
                //以类名为key，保存订阅方法
                List<ExecutableElement> methods = methodsByClass.get(classElement);
                if (methods == null) {
                    methods = new ArrayList<>();
                    methodsByClass.put(classElement, methods);
                }
                methods.add(method);
            }
        }
    }

    private void createFile(TypeElement subscribeIndexType) {
        //添加静态块代码：SUBSCRIBER_INDEX = new HashMap<Class, SubscribeInfo>();
        CodeBlock.Builder codeBlock = CodeBlock.builder();
        codeBlock.addStatement("$N = new $T<$T, $T>()",
                Constants.FIELD_NAME,
                HashMap.class,
                Class.class,
                SubscribeInfo.class);

        for (Map.Entry<TypeElement, List<ExecutableElement>> entry : methodsByClass.entrySet()) {
            CodeBlock.Builder contentBlock = CodeBlock.builder();
            CodeBlock contentCode = null;
            String format;
            List<ExecutableElement> elements = entry.getValue();
            int size = elements.size();
            for (int i = 0; i < size; i++) {
                ExecutableElement element = elements.get(i);
                //获取每个方法上的@Subscribe注解中的注解值
                Subscribe subscribe = element.getAnnotation(Subscribe.class);
                //获取订阅事件方法所有参数
                List<? extends VariableElement> parameters = element.getParameters();
                //获取订阅事件方法名
                String methodName = element.getSimpleName().toString();
                // 注意：此处还可以做检查工作，比如：参数类型必须是类或接口类型（这里缩减了）
                TypeElement parameterElement = (TypeElement) typeUtils.asElement(parameters.get(0).asType());
                // 如果是最后一个添加，则无需逗号结尾
                if (i == size - 1) {
                    format = "new $T($T.class, $S, $T.class, $T.$L, $L, $L)";
                } else {
                    format = "new $T($T.class, $S, $T.class, $T.$L, $L, $L),\n";
                }
                // new SubscriberMethod(MainActivity.class, "abc", UserInfo.class, ThreadMode.POSTING, 0, false)
                contentCode = contentBlock.add(format,
                        SubscriberMethod.class,
                        ClassName.get(entry.getKey()),
                        methodName,
                        ClassName.get(parameterElement),
                        ThreadMode.class,
                        subscribe.threadMode(),
                        subscribe.priority(),
                        subscribe.sticky())
                        .build();
            }
            if (contentCode != null) {
                // putIndex(new EventBeans(MainActivity.class, new SubscriberMethod[] {)
                codeBlock.beginControlFlow("putIndex(new $T($T.class, new $T[]",
                        EventBeans.class,
                        ClassName.get(entry.getKey()),
                        SubscriberMethod.class)
                        // 嵌套的精华（尝试了很多次，有更好的方式请告诉我）
                        .add(contentCode)
                        // ))}
                        .endControlFlow("))");
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "注解处理器双层循环发生错误！");
            }
        }

        // 全局属性：Map<Class<?>, SubscriberMethod>
        TypeName fieldType = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(Class.class), // Map<Class,
                ClassName.get(SubscribeInfo.class) // Map<Class, SubscriberMethod>
        );

        // putIndex方法参数：putIndex(SubscriberInfo info)
        ParameterSpec putIndexParameter = ParameterSpec.builder(
                ClassName.get(SubscribeInfo.class),
                Constants.PUTINDEX_PARAMETER_NAME)
                .build();

        // putIndex方法配置：private static void putIndex(SubscriberMethod info) {
        MethodSpec.Builder putIndexBuidler = MethodSpec
                .methodBuilder(Constants.PUTINDEX_METHOD_NAME) // 方法名
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC) // private static修饰符
                .addParameter(putIndexParameter); // 添加方法参数

        // putIndex方法内容：SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
        putIndexBuidler.addStatement("$N.put($N.getSubscriberClass(), $N)",
                Constants.FIELD_NAME,
                Constants.PUTINDEX_PARAMETER_NAME,
                Constants.PUTINDEX_PARAMETER_NAME);

        // getSubscriberInfo方法参数：Class subscriberClass
        ParameterSpec getSubscriberInfoParameter = ParameterSpec.builder(
                ClassName.get(Class.class),
                Constants.GETSUBSCRIBERINFO_PARAMETER_NAME)
                .build();


        //getSubscriberInfo方法配置：public SubscriberMethod getSubscribeInfo(Class<?> subscriberClass) {}
        MethodSpec.Builder getSubscriberInfoBuilder = MethodSpec.methodBuilder(Constants.GETSUBSCRIBERINFO_METHOD_NAME)
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(getSubscriberInfoParameter)
                .returns(SubscribeInfo.class);
        //getSubscriberInfo方法的内容： return SUBSCRIBER_INDEX.get(subscriberClass);
        getSubscriberInfoBuilder.addStatement("return $N.get($N)",
                Constants.FIELD_NAME,
                Constants.GETSUBSCRIBERINFO_PARAMETER_NAME);

        // 构造类
        TypeSpec typeSpec = TypeSpec.classBuilder(className)
                //实现SubscribeInfoIndex接口
                .addSuperinterface(ClassName.get(subscribeIndexType))
                //改类的修饰符
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                //添加静态块（很少用的api）
                .addStaticBlock(codeBlock.build())
                //全局属性:private static final Map<Class<?>, SubscriberMethod> SUBSCRIBER_INDEX
                .addField(fieldType, Constants.FIELD_NAME, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
                //第一个方法：加入全局Map集合
                .addMethod(putIndexBuidler.build())
                //第二个方法：通过订阅者对象（MainActivity.class）获取所有订阅方法
                .addMethod(getSubscriberInfoBuilder.build())
                .build();
        // 生成类文件：EventBusIndex
        try {
            JavaFile.builder(packageName, typeSpec)
                    .build()
                    .writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 检验@Subscribe注解的方法格式是否真确
     * 条件：订阅方法必须是非静态,公开的，参数只有一个
     *
     * @param element 方法元素
     * @return
     */
    private boolean checkMethod(ExecutableElement element) {
        //不能为static的静态方法
        if (element.getModifiers().contains(Modifier.STATIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "订阅方法不能为static的静态方法", element);
            return false;
        }
        if (!element.getModifiers().contains(Modifier.PUBLIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "订阅方法必须是public修饰的方法", element);
            return false;
        }
        List<? extends VariableElement> parameters = element.getParameters();
        if (parameters.size() != 1) {
            messager.printMessage(Diagnostic.Kind.ERROR, "订阅方法有且只有一个参数", element);
            return false;
        }
        return true;
    }
}
